// webgl_advanced/webgl_lightningstrike.js
import {document,window,requestAnimationFrame,cancelAnimationFrame,Event,core} from 'dhtml-weixin';
import * as THREE from '../three/Three.js';
import Stats from './jsm/libs/stats.module.js';
import { GUI } from './jsm/libs/lil-gui.module.min.js';
import { OrbitControls } from './jsm/controls/OrbitControls.js';
import { LightningStrike } from './jsm/geometries/LightningStrike.js';
import { LightningStorm } from './jsm/objects/LightningStorm.js';
import { EffectComposer } from './jsm/postprocessing/EffectComposer.js';
import { RenderPass } from './jsm/postprocessing/RenderPass.js';
import { OutlinePass } from './jsm/postprocessing/OutlinePass.js';


var requestId
Page({
	onUnload() {
		cancelAnimationFrame(requestId, this.canvas)
this.worker && this.worker.terminate()
		setTimeout(() => {
			if (this.renderer instanceof THREE.WebGLRenderer) {
				this.renderer.dispose()
				this.renderer.forceContextLoss()
				this.renderer.context = null
				this.renderer.domElement = null
				this.renderer = null
			}
		}, 0)
	},
	    webgl_touch(e) {
        const web_e = Event.fix(e)
        //window.dispatchEvent(web_e)
        //document.dispatchEvent(web_e)
        this.canvas.dispatchEvent(web_e)
    },
  async onLoad(){
const canvas3d = this.canvas =await document.createElementAsync("canvas","webgl")
var that = this

let container, stats;

let scene, renderer, composer, gui;

let currentSceneIndex = 0;

let currentTime = 0;

const sceneCreators = [
  createConesScene,
  createPlasmaBallScene,
  createStormScene
];

const clock = new THREE.Clock();

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

init();
animate();

function init() {

  container = document.getElementById( 'container' );

  renderer = that.renderer = new THREE.WebGLRenderer({canvas:canvas3d});
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  renderer.outputEncoding = THREE.sRGBEncoding;

  container.appendChild( renderer.domElement );

  composer = new EffectComposer( renderer );

  stats = new Stats();
  container.appendChild( stats.dom );

  window.addEventListener( 'resize', onWindowResize );

  createScene();

}

function createScene() {

  scene = sceneCreators[ currentSceneIndex ]();

  createGUI();

}

function onWindowResize() {

  scene.userData.camera.aspect = window.innerWidth / window.innerHeight;
  scene.userData.camera.updateProjectionMatrix();

  renderer.setSize( window.innerWidth, window.innerHeight );

  composer.setSize( window.innerWidth, window.innerHeight );

}

//

function createGUI() {

  if ( gui ) {

    gui.destroy();

  }

  gui = new GUI( { width: 315 } );

  const sceneFolder = gui.addFolder( 'Scene' );

  scene.userData.sceneIndex = currentSceneIndex;

  sceneFolder.add( scene.userData, 'sceneIndex', { 'Electric Cones': 0, 'Plasma Ball': 1, 'Storm': 2 } ).name( 'Scene' ).onChange( function ( value ) {

    currentSceneIndex = value;

    createScene();

  } );

  scene.userData.timeRate = 1;
  sceneFolder.add( scene.userData, 'timeRate', scene.userData.canGoBackwardsInTime ? - 1 : 0, 1 ).name( 'Time rate' );

  sceneFolder.open();

  const graphicsFolder = gui.addFolder( 'Graphics' );

  graphicsFolder.add( scene.userData, 'outlineEnabled' ).name( 'Glow enabled' );

  scene.userData.lightningColorRGB = [
    scene.userData.lightningColor.r * 255,
    scene.userData.lightningColor.g * 255,
    scene.userData.lightningColor.b * 255
  ];
  graphicsFolder.addColor( scene.userData, 'lightningColorRGB' ).name( 'Color' ).onChange( function ( value ) {

    scene.userData.lightningMaterial.color.setRGB( value[ 0 ], value[ 1 ], value[ 2 ] ).multiplyScalar( 1 / 255 );

  } );
  scene.userData.outlineColorRGB = [
    scene.userData.outlineColor.r * 255,
    scene.userData.outlineColor.g * 255,
    scene.userData.outlineColor.b * 255
  ];
  graphicsFolder.addColor( scene.userData, 'outlineColorRGB' ).name( 'Glow color' ).onChange( function ( value ) {

    scene.userData.outlineColor.setRGB( value[ 0 ], value[ 1 ], value[ 2 ] ).multiplyScalar( 1 / 255 );

  } );

  graphicsFolder.open();

  const rayFolder = gui.addFolder( 'Ray parameters' );

  rayFolder.add( scene.userData.rayParams, 'straightness', 0, 1 ).name( 'Straightness' );
  rayFolder.add( scene.userData.rayParams, 'roughness', 0, 1 ).name( 'Roughness' );
  rayFolder.add( scene.userData.rayParams, 'radius0', 0.1, 10 ).name( 'Initial radius' );
  rayFolder.add( scene.userData.rayParams, 'radius1', 0.1, 10 ).name( 'Final radius' );
  rayFolder.add( scene.userData.rayParams, 'radius0Factor', 0, 1 ).name( 'Subray initial radius' );
  rayFolder.add( scene.userData.rayParams, 'radius1Factor', 0, 1 ).name( 'Subray final radius' );
  rayFolder.add( scene.userData.rayParams, 'timeScale', 0, 5 ).name( 'Ray time scale' );
  rayFolder.add( scene.userData.rayParams, 'subrayPeriod', 0.1, 10 ).name( 'Subray period (s)' );
  rayFolder.add( scene.userData.rayParams, 'subrayDutyCycle', 0, 1 ).name( 'Subray duty cycle' );

  if ( scene.userData.recreateRay ) {

    // Parameters which need to recreate the ray after modification

    const raySlowFolder = gui.addFolder( 'Ray parameters (slow)' );

    raySlowFolder.add( scene.userData.rayParams, 'ramification', 0, 15 ).step( 1 ).name( 'Ramification' ).onFinishChange( function () {

      scene.userData.recreateRay();

    } );

    raySlowFolder.add( scene.userData.rayParams, 'maxSubrayRecursion', 0, 5 ).step( 1 ).name( 'Recursion' ).onFinishChange( function () {

      scene.userData.recreateRay();

    } );

    raySlowFolder.add( scene.userData.rayParams, 'recursionProbability', 0, 1 ).name( 'Rec. probability' ).onFinishChange( function () {

      scene.userData.recreateRay();

    } );

    raySlowFolder.open();

  }

  rayFolder.open();

}

//

function animate() {

  requestAnimationFrame(animate);

  render();
  stats.update();

}

function render() {

  currentTime += scene.userData.timeRate * clock.getDelta();

  if ( currentTime < 0 ) {

    currentTime = 0;

  }

  scene.userData.render( currentTime );

}

function createOutline( scene, objectsArray, visibleColor ) {

  const outlinePass = new OutlinePass( new THREE.Vector2( window.innerWidth, window.innerHeight ), scene, scene.userData.camera, objectsArray );
  outlinePass.edgeStrength = 2.5;
  outlinePass.edgeGlow = 0.7;
  outlinePass.edgeThickness = 2.8;
  outlinePass.visibleEdgeColor = visibleColor;
  outlinePass.hiddenEdgeColor.set( 0 );
  composer.addPass( outlinePass );

  scene.userData.outlineEnabled = true;

  return outlinePass;

}

//

function createConesScene() {

  const scene = new THREE.Scene();
  scene.background = new THREE.Color( 0x050505 );

  scene.userData.canGoBackwardsInTime = true;

  scene.userData.camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 200, 100000 );

  // Lights

  scene.userData.lightningColor = new THREE.Color( 0xB0FFFF );
  scene.userData.outlineColor = new THREE.Color( 0x00FFFF );

  const posLight = new THREE.PointLight( 0x00ffff, 1, 5000, 2 );
  scene.add( posLight );

  // Ground

  const ground = new THREE.Mesh( new THREE.PlaneGeometry( 200000, 200000 ), new THREE.MeshPhongMaterial( { color: 0xC0C0C0, shininess: 0 } ) );
  ground.rotation.x = - Math.PI * 0.5;
  scene.add( ground );

  // Cones

  const conesDistance = 1000;
  const coneHeight = 200;
  const coneHeightHalf = coneHeight * 0.5;

  posLight.position.set( 0, ( conesDistance + coneHeight ) * 0.5, 0 );
  posLight.color = scene.userData.outlineColor;

  scene.userData.camera.position.set( 5 * coneHeight, 4 * coneHeight, 18 * coneHeight );

  const coneMesh1 = new THREE.Mesh( new THREE.ConeGeometry( coneHeight, coneHeight, 30, 1, false ), new THREE.MeshPhongMaterial( { color: 0xFFFF00, emissive: 0x1F1F00 } ) );
  coneMesh1.rotation.x = Math.PI;
  coneMesh1.position.y = conesDistance + coneHeight;
  scene.add( coneMesh1 );

  const coneMesh2 = new THREE.Mesh( coneMesh1.geometry.clone(), new THREE.MeshPhongMaterial( { color: 0xFF2020, emissive: 0x1F0202 } ) );
  coneMesh2.position.y = coneHeightHalf;
  scene.add( coneMesh2 );

  // Lightning strike

  scene.userData.lightningMaterial = new THREE.MeshBasicMaterial( { color: scene.userData.lightningColor } );

  scene.userData.rayParams = {

    sourceOffset: new THREE.Vector3(),
    destOffset: new THREE.Vector3(),
    radius0: 4,
    radius1: 4,
    minRadius: 2.5,
    maxIterations: 7,
    isEternal: true,

    timeScale: 0.7,

    propagationTimeFactor: 0.05,
    vanishingTimeFactor: 0.95,
    subrayPeriod: 3.5,
    subrayDutyCycle: 0.6,
    maxSubrayRecursion: 3,
    ramification: 7,
    recursionProbability: 0.6,

    roughness: 0.85,
    straightness: 0.6

  };

  let lightningStrike;
  let lightningStrikeMesh;
  const outlineMeshArray = [];

  scene.userData.recreateRay = function () {

    if ( lightningStrikeMesh ) {

      scene.remove( lightningStrikeMesh );

    }

    lightningStrike = new LightningStrike( scene.userData.rayParams );
    lightningStrikeMesh = new THREE.Mesh( lightningStrike, scene.userData.lightningMaterial );

    outlineMeshArray.length = 0;
    outlineMeshArray.push( lightningStrikeMesh );

    scene.add( lightningStrikeMesh );

  };

  scene.userData.recreateRay();

  // Compose rendering

  composer.passes = [];
  composer.addPass( new RenderPass( scene, scene.userData.camera ) );
  createOutline( scene, outlineMeshArray, scene.userData.outlineColor );

  // Controls

  const controls = new OrbitControls( scene.userData.camera, renderer.domElement );
  controls.target.y = ( conesDistance + coneHeight ) * 0.5;
  controls.enableDamping = true;
  controls.dampingFactor = 0.05;

  scene.userData.render = function ( time ) {

    // Move cones and Update ray position
    coneMesh1.position.set( Math.sin( 0.5 * time ) * conesDistance * 0.6, conesDistance + coneHeight, Math.cos( 0.5 * time ) * conesDistance * 0.6 );
    coneMesh2.position.set( Math.sin( 0.9 * time ) * conesDistance, coneHeightHalf, 0 );
    lightningStrike.rayParameters.sourceOffset.copy( coneMesh1.position );
    lightningStrike.rayParameters.sourceOffset.y -= coneHeightHalf;
    lightningStrike.rayParameters.destOffset.copy( coneMesh2.position );
    lightningStrike.rayParameters.destOffset.y += coneHeightHalf;

    lightningStrike.update( time );

    controls.update();

    // Update point light position to the middle of the ray
    posLight.position.lerpVectors( lightningStrike.rayParameters.sourceOffset, lightningStrike.rayParameters.destOffset, 0.5 );

    if ( scene.userData.outlineEnabled ) {

      composer.render();

    }	else {

      renderer.render( scene, scene.userData.camera );

    }

  };

  return scene;

}

//

function createPlasmaBallScene() {

  const scene = new THREE.Scene();

  scene.userData.canGoBackwardsInTime = true;

  scene.userData.camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 100, 50000 );

  const ballScene = new THREE.Scene();
  ballScene.background = new THREE.Color( 0x454545 );

  // Lights

  const ambientLight = new THREE.AmbientLight( 0x444444 );
  ballScene.add( ambientLight );
  scene.add( ambientLight );

  const light1 = new THREE.DirectionalLight( 0xffffff, 0.5 );
  light1.position.set( 1, 1, 1 );
  ballScene.add( light1 );
  scene.add( light1 );

  const light2 = new THREE.DirectionalLight( 0xffffff, 1.5 );
  light2.position.set( - 0.5, 1, 0.2 );
  ballScene.add( light2 );
  scene.add( light2 );

  // Plasma ball

  scene.userData.lightningColor = new THREE.Color( 0xFFB0FF );
  scene.userData.outlineColor = new THREE.Color( 0xFF00FF );

  scene.userData.lightningMaterial = new THREE.MeshBasicMaterial( { color: scene.userData.lightningColor, side: THREE.DoubleSide } );

  const r = 'textures/cube/Bridge2/';
  const urls = [ r + 'posx.jpg', r + 'negx.jpg',
         r + 'posy.jpg', r + 'negy.jpg',
         r + 'posz.jpg', r + 'negz.jpg' ];

  const textureCube = new THREE.CubeTextureLoader().load( urls );
  textureCube.mapping = THREE.CubeReflectionMapping;
  textureCube.encoding = THREE.sRGBEncoding;

  const sphereMaterial = new THREE.MeshPhysicalMaterial( {
    transparent: true,
    transmission: .96,
    depthWrite: false,
    color: 'white',
    metalness: 0,
    roughness: 0,
    envMap: textureCube
  } );

  const sphereHeight = 300;
  const sphereRadius = 200;

  scene.userData.camera.position.set( 5 * sphereRadius, 2 * sphereHeight, 6 * sphereRadius );

  const sphereMesh = new THREE.Mesh( new THREE.SphereGeometry( sphereRadius, 80, 40 ), sphereMaterial );
  sphereMesh.position.set( 0, sphereHeight, 0 );
  ballScene.add( sphereMesh );

  const sphere = new THREE.Sphere( sphereMesh.position, sphereRadius );

  const spherePlasma = new THREE.Mesh( new THREE.SphereGeometry( sphereRadius * 0.05, 24, 12 ), scene.userData.lightningMaterial );
  spherePlasma.position.copy( sphereMesh.position );
  spherePlasma.scale.y = 0.6;
  scene.add( spherePlasma );

  const post = new THREE.Mesh(
    new THREE.CylinderGeometry( sphereRadius * 0.06, sphereRadius * 0.06, sphereHeight, 6, 1, true ),
    new THREE.MeshLambertMaterial( { color: 0x020202 } )
  );
  post.position.y = sphereHeight * 0.5 - sphereRadius * 0.05 * 1.2;
  scene.add( post );

  const box = new THREE.Mesh( new THREE.BoxGeometry( sphereHeight * 0.5, sphereHeight * 0.1, sphereHeight * 0.5 ), post.material );
  box.position.y = sphereHeight * 0.05 * 0.5;
  scene.add( box );

  const rayDirection = new THREE.Vector3();
  let rayLength = 0;
  const vec1 = new THREE.Vector3();
  const vec2 = new THREE.Vector3();

  scene.userData.rayParams = {

    sourceOffset: sphereMesh.position,
    destOffset: new THREE.Vector3( sphereRadius, 0, 0 ).add( sphereMesh.position ),
    radius0: 4,
    radius1: 4,
    radius0Factor: 0.82,
    minRadius: 2.5,
    maxIterations: 6,
    isEternal: true,

    timeScale: 0.6,
    propagationTimeFactor: 0.15,
    vanishingTimeFactor: 0.87,
    subrayPeriod: 0.8,
    ramification: 5,
    recursionProbability: 0.8,

    roughness: 0.85,
    straightness: 0.7,

    onSubrayCreation: function ( segment, parentSubray, childSubray, lightningStrike ) {

      lightningStrike.subrayConePosition( segment, parentSubray, childSubray, 0.6, 0.9, 0.7 );

      // THREE.Sphere projection

      vec1.subVectors( childSubray.pos1, lightningStrike.rayParameters.sourceOffset );
      vec2.set( 0, 0, 0 );

      if ( lightningStrike.randomGenerator.random() < 0.7 ) {

        vec2.copy( rayDirection ).multiplyScalar( rayLength * 1.0865 );

      }

      vec1.add( vec2 ).setLength( rayLength );
      childSubray.pos1.addVectors( vec1, lightningStrike.rayParameters.sourceOffset );

    }

  };

  let lightningStrike;
  let lightningStrikeMesh;
  const outlineMeshArray = [];

  scene.userData.recreateRay = function () {

    if ( lightningStrikeMesh ) {

      scene.remove( lightningStrikeMesh );

    }

    lightningStrike = new LightningStrike( scene.userData.rayParams );
    lightningStrikeMesh = new THREE.Mesh( lightningStrike, scene.userData.lightningMaterial );

    outlineMeshArray.length = 0;
    outlineMeshArray.push( lightningStrikeMesh );
    outlineMeshArray.push( spherePlasma );

    scene.add( lightningStrikeMesh );

  };

  scene.userData.recreateRay();

  // Compose rendering

  composer.passes = [];

  composer.addPass( new RenderPass( ballScene, scene.userData.camera ) );

  const rayPass = new RenderPass( scene, scene.userData.camera );
  rayPass.clear = false;
  composer.addPass( rayPass );

  const outlinePass = createOutline( scene, outlineMeshArray, scene.userData.outlineColor );

  scene.userData.render = function ( time ) {

    rayDirection.subVectors( lightningStrike.rayParameters.destOffset, lightningStrike.rayParameters.sourceOffset );
    rayLength = rayDirection.length();
    rayDirection.normalize();

    lightningStrike.update( time );

    controls.update();

    outlinePass.enabled = scene.userData.outlineEnabled;

    composer.render();

  };

  // Controls

  const controls = new OrbitControls( scene.userData.camera, renderer.domElement );
  controls.target.copy( sphereMesh.position );
  controls.enableDamping = true;
  controls.dampingFactor = 0.05;

  // THREE.Sphere mouse raycasting

  container.style.touchAction = 'none';
  container.addEventListener( 'pointermove', onPointerMove );

  function onPointerMove( event ) {

    if ( event.isPrimary === false ) return;

    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

    checkIntersection();

  }

  const intersection = new THREE.Vector3();

  function checkIntersection() {

    raycaster.setFromCamera( mouse, scene.userData.camera );

    const result = raycaster.ray.intersectSphere( sphere, intersection );

    if ( result !== null ) {

      lightningStrike.rayParameters.destOffset.copy( intersection );

    }

  }

  return scene;

}

//

function createStormScene() {

  const scene = new THREE.Scene();
  scene.background = new THREE.Color( 0x050505 );

  scene.userData.canGoBackwardsInTime = false;

  scene.userData.camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 20, 10000 );

  // Lights

  scene.add( new THREE.AmbientLight( 0x444444 ) );

  const light1 = new THREE.DirectionalLight( 0xffffff, 0.5 );
  light1.position.set( 1, 1, 1 );
  scene.add( light1 );

  const posLight = new THREE.PointLight( 0x00ffff );
  posLight.position.set( 0, 100, 0 );
  scene.add( posLight );

  // Ground

  const GROUND_SIZE = 1000;

  scene.userData.camera.position.set( 0, 0.2, 1.6 ).multiplyScalar( GROUND_SIZE * 0.5 );

  const ground = new THREE.Mesh( new THREE.PlaneGeometry( GROUND_SIZE, GROUND_SIZE ), new THREE.MeshLambertMaterial( { color: 0x072302 } ) );
  ground.rotation.x = - Math.PI * 0.5;
  scene.add( ground );

  // Storm

  scene.userData.lightningColor = new THREE.Color( 0xB0FFFF );
  scene.userData.outlineColor = new THREE.Color( 0x00FFFF );

  scene.userData.lightningMaterial = new THREE.MeshBasicMaterial( { color: scene.userData.lightningColor } );

  const rayDirection = new THREE.Vector3( 0, - 1, 0 );
  let rayLength = 0;
  const vec1 = new THREE.Vector3();
  const vec2 = new THREE.Vector3();

  scene.userData.rayParams = {

    radius0: 1,
    radius1: 0.5,
    minRadius: 0.3,
    maxIterations: 7,

    timeScale: 0.15,
    propagationTimeFactor: 0.2,
    vanishingTimeFactor: 0.9,
    subrayPeriod: 4,
    subrayDutyCycle: 0.6,

    maxSubrayRecursion: 3,
    ramification: 3,
    recursionProbability: 0.4,

    roughness: 0.85,
    straightness: 0.65,

    onSubrayCreation: function ( segment, parentSubray, childSubray, lightningStrike ) {

      lightningStrike.subrayConePosition( segment, parentSubray, childSubray, 0.6, 0.6, 0.5 );

      // Plane projection

      rayLength = lightningStrike.rayParameters.sourceOffset.y;
      vec1.subVectors( childSubray.pos1, lightningStrike.rayParameters.sourceOffset );
      const proj = rayDirection.dot( vec1 );
      vec2.copy( rayDirection ).multiplyScalar( proj );
      vec1.sub( vec2 );
      const scale = proj / rayLength > 0.5 ? rayLength / proj : 1;
      vec2.multiplyScalar( scale );
      vec1.add( vec2 );
      childSubray.pos1.addVectors( vec1, lightningStrike.rayParameters.sourceOffset );

    }

  };

  // Black star mark
  const starVertices = [];
  const prevPoint = new THREE.Vector3( 0, 0, 1 );
  const currPoint = new THREE.Vector3();
  for ( let i = 1; i <= 16; i ++ ) {

    currPoint.set( Math.sin( 2 * Math.PI * i / 16 ), 0, Math.cos( 2 * Math.PI * i / 16 ) );

    if ( i % 2 === 1 ) {

      currPoint.multiplyScalar( 0.3 );

    }

    starVertices.push( 0, 0, 0 );
    starVertices.push( prevPoint.x, prevPoint.y, prevPoint.z );
    starVertices.push( currPoint.x, currPoint.y, currPoint.z );

    prevPoint.copy( currPoint );

  }

  const starGeometry = new THREE.BufferGeometry();
  starGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( starVertices, 3 ) );
  const starMesh = new THREE.Mesh( starGeometry, new THREE.MeshBasicMaterial( { color: 0x020900 } ) );
  starMesh.scale.multiplyScalar( 6 );

  //

  const storm = new LightningStorm( {

    size: GROUND_SIZE,
    minHeight: 90,
    maxHeight: 200,
    maxSlope: 0.6,
    maxLightnings: 8,

    lightningParameters: scene.userData.rayParams,

    lightningMaterial: scene.userData.lightningMaterial,

    onLightningDown: function ( lightning ) {

      // Add black star mark at ray strike
      const star1 = starMesh.clone();
      star1.position.copy( lightning.rayParameters.destOffset );
      star1.position.y = 0.05;
      star1.rotation.y = 2 * Math.PI * Math.random();
      scene.add( star1 );

    }

  } );

  scene.add( storm );

  // Compose rendering

  composer.passes = [];
  composer.addPass( new RenderPass( scene, scene.userData.camera ) );
  createOutline( scene, storm.lightningsMeshes, scene.userData.outlineColor );

  // Controls

  const controls = new OrbitControls( scene.userData.camera, renderer.domElement );
  controls.target.y = GROUND_SIZE * 0.05;
  controls.enableDamping = true;
  controls.dampingFactor = 0.05;

  scene.userData.render = function ( time ) {

    storm.update( time );

    controls.update();

    if ( scene.userData.outlineEnabled ) {

      composer.render();

    }	else {

      renderer.render( scene, scene.userData.camera );

    }

  };

  return scene;

}
}
})